<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MutationObserverInit 与观察范围</title>
  </head>
  <body></body>
  <script type="text/javascript">
    /** 1.MutationObserverInit 对象用于控制对目标节点的观察范围。粗略地讲，观察者可以观察的事件包括属性变化、文本变化和子节点变化。
     *    a.MutationObserverInit 对象的属性。('subtree','attributes','attributeFilter':[attributeName],
     *      attributeOldValue,characterData,characterDataOldValue,childList)。
     *    b.在调用 observe() 时，MutationObserverInit 对象中的 attribute、characterData和 childList 属性必须至少有一项为 true（无论是直接设置这几个
     *      属性，还是通过设置 attributeOldValue 等属性间接导致它们的值转换为 true）。否则会抛出错误，因为没有任何变化事件可能触发回调。
     * **/

    /** 2.观察属性
     *    a.MutationObserver 可以观察节点属性的添加、移除和修改。要为属性变化注册回调，需要在MutationObserverInit 对象中将 attributes 属性设置为
     *      true。{ attributes: true }；
     *    b.如果想观察某个或某几个属性，可以使用 attributeFilter 属性来设置白名单，即一个属性名字符串数组：{ attributeFilter: ['foo'] }；
     *    c.如果想在变化记录中保存属性原来的值，可以将 attributeOldValue 属性设置为 true： { attributeOldValue: true },默认会自动触发
     *      attributes为true；
     *
     * **/

    function checkAttributeRange(flag) {
      //1.创建三个Element节点,并添加到DOM中
      let divDOM = document.createElement("div");
      let spanDOM = document.createElement("span");
      let pDOM = document.createElement("p");
      let fragment = document.createDocumentFragment();
      fragment.append(divDOM, spanDOM, pDOM);
      document.body.appendChild(fragment);
      //2.测试divDOM,监听属性的添加，修改，和删除
      if (flag === "a") {
        let mutationObserver = new MutationObserver((MutationRecord) => {
          console.log(MutationRecord);
        });
        mutationObserver.observe(divDOM, { attributes: true });
        //添加属性
        divDOM.setAttribute("foo", "bar");
        //修改属性
        divDOM.setAttribute("foo", "baz");
        //删除属性
        divDOM.removeAttribute("foo");
      }
      //3.测试 spanDOM ，监听多个属性的变化
      if (flag === "b") {
        let mutationObserver = new MutationObserver((MutationRecord) => {
          console.log(MutationRecord);
        });
        mutationObserver.observe(spanDOM, { attributeFilter: ["foo"] });
        //添加属性 foo
        spanDOM.setAttribute("foo", "bar");
        //添加属性 baz
        spanDOM.setAttribute("baz", "qux");
      }
      //4.测试 pDOM ，变化记录中保存属性原来的值
      if (flag === "c") {
        let mutationObserver = new MutationObserver((MutationRecord) => {
          console.log(MutationRecord.map((x) => x.oldValue));
        });
        mutationObserver.observe(pDOM, { attributeOldValue: true });
        //添加属性 foo
        pDOM.setAttribute("foo", "bar");
        //修改属性 foo
        pDOM.setAttribute("foo", "qux");
        //修改属性 foo
        pDOM.setAttribute("foo", "wxy");
      }

      console.log("attributed changed!!");
    }
    // checkAttributeRange('a');//[MutationRecord, MutationRecord, MutationRecord]
    // checkAttributeRange('b');//[MutationRecord]
    // checkAttributeRange('c');//[null, 'bar', 'qux']

    /** 3.观察字符数据
     *    a.MutationObserver 可以观察文本节点（如 Text、Comment 或 ProcessingInstruction 节点）中字符的添加、删除和修改。要为
     *      字符数据注册回调，需要在 MutationObserverInit 对象中将 characterData 属性设置为 true.
     *    b.将 characterData 属性设置为 true 的默认行为不会在 MutationRecord 对象中记录原来的字符数据。如果想在变化记录中保存原来
     *      的字符数据，可以将 characterDataOldValue 属性设置为 true.
     *    c.设置元素文本内容的标准方式是 textContent 属性。Element 类也定义了 innerText 属性，与 textContent 类似。但
     *      innerText 的定义不严谨，浏览器间的实现也存在兼容性问题，因此不建议再使用了。
     * **/
    function checkCharacterData(flag) {
      //1.创建Element节点并添加到页面中
      let divDOM = document.createElement("div");
      divDOM.appendChild(document.createTextNode("Hello TiKo"));
      let spanDOM = document.createElement("span");
      spanDOM.appendChild(document.createComment("comment"));
      document.body.append(divDOM, spanDOM);
      //2.配置 mutationObserver监听
      if (flag === "a") {
        let mutationObserver = new MutationObserver((records) =>
          console.log(records)
        );
        //监听文本节点
        mutationObserver.observe(divDOM.firstChild, { characterData: true });
        //监听注释节点
        mutationObserver.observe(spanDOM.firstChild, { characterData: true });
        //修改文本节点
        divDOM.firstChild.textContent = "hello world";
        //修改注释节点
        spanDOM.firstChild.nodeValue = "COMMENT";
      }
    }
    // checkCharacterData('a')

    /** 4.观察子节点
     *    a.MutationObserver 可以观察目标节点子节点的添加和移除。要观察子节点，需要在 MutationObserverInit 对象中将 childList 属性设置为 true。
     *    b.对子节点重新排序（尽管调用一个方法即可实现）会报告两次变化事件，因为从技术上会涉及先移除和再添加。
     *    c.
     * **/
    function checkChildNode() {
      //1.清空主体
      document.body.innerHTML = "";
      //2.创建mutationObserver实例
      let observer = new MutationObserver((mutationRecords) =>
        console.log(mutationRecords)
      );
      //3.创建两个初始子节点
      document.body.appendChild(document.createElement("div"));
      document.body.appendChild(document.createElement("span"));
      //4.实例绑定对象进行监听
      observer.observe(document.body, { childList: true });
      document.body.appendChild(document.createElement("div"));
      //5.将firstChild插入lastChild后面
      document.body.insertBefore(
        document.body.firstChild,
        document.body.lastChild
      );
    }
    // checkChildNode();

    /** 5.观察子树
     *    a.默认情况下，MutationObserver 将观察的范围限定为一个元素及其子节点的变化。可以把观察的范围扩展到这个元素的子树（所有后代
     *      节点），这需要在MutationObserverInit 对象中将 subtree 属性设置为 true。
     *    b.被观察子树中的节点被移出子树之后仍然能够触发变化事件。这意味着在子树中的节点离开该子树后，即使严格来讲该节点已经脱离了原来的
     *      子树，但它仍然会触发变化事件。
     *
     * **/

    function checkSubtree(flag) {
      // 1.清空主体
      document.body.innerHTML = "";
      if (flag === 'a') {
        // 2.创建Element并添加到document.body中
        let divDOM = document.createElement("div");
        document.body.insertBefore(divDOM, null);
        // 3.创建mutationObserver实例
        let mutationObserver = new MutationObserver((mutationRecords) => {console.log(mutationRecords);});
        // 4.将监听实例绑定到 document.body 中，并设置子树监听
        mutationObserver.observe(document.body, {
          attributes: true,
          subtree: true,
        });
        // 5.修改子树节点
        divDOM.setAttribute("foo", "bar");
      }

      if(flag === 'b') {
        // 2.创建包含两层的子树；
        let subtreeRoot = document.createElement('div');
        let subtreeLeef = document.createElement('span');
        // 3.将子树添加到页面中；
        document.body.insertBefore(subtreeRoot,null);
        subtreeRoot.insertBefore(subtreeLeef,null);
        // 4.创建 MutationObserver 监听；
        let mutationObserver = new MutationObserver(mutationRecords=>console.log(mutationRecords));
        // 5.监听 subtreeRoot ,监听子树的属性变化；
        mutationObserver.observe(subtreeRoot,{ attributes:true, subtree:true });
        // 6.将 subtreeLeef 取出来，并与 subtreeRoot 并列;
        document.body.insertBefore(subtreeLeef,subtreeRoot);
        // 7.修改 subtreeLeef 属性；
        subtreeLeef.setAttribute('foo','bar');
      }
    }
    // checkSubtree("a"); //[MutationRecord]
    // checkSubtree("b"); //[MutationRecord]
  </script>
</html>
